#include "DataProxy.h"
#include <sstream>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <string>
#include <boost/thread.hpp>
#include "Config.hpp"

using namespace netspy::db;
using namespace std;
using namespace boost;

mutex DataProxy::singletonWatchdog;
shared_ptr<DataProxy> DataProxy::instance; 

shared_ptr<DataProxy> DataProxy::getInstance(){
    if(DataProxy::instance==NULL){
        DataProxy::singletonWatchdog.lock();
        if(DataProxy::instance==NULL){
            Config & config = Config::getInstance();
            DataProxy::instance = shared_ptr<DataProxy>(new DataProxy(
                    string(config.getDatabaseName()),
                    string(config.getDatabaseUser()),
                    string(config.getDatabasePass())
                    ));
        }
        DataProxy::singletonWatchdog.unlock();
    }
    return DataProxy::instance;
}

DataProxy::DataProxy(string database, string user, string pass) {
    this->db = cppdb::session("mysql:database=" + database + ";user=" + user + ";password=" + pass);
}

DataProxy::DataProxy(const DataProxy& orig) {
    this->db = orig.db;
}

DataProxy::~DataProxy() {
    db.close();
}

boost::shared_ptr<const std::vector<boost::shared_ptr<Client> > > DataProxy::getClients() const{
    return resToClients(query("client"));
}

boost::shared_ptr<const vector<boost::shared_ptr<Reply> > > DataProxy::getReplies() const {
    return resToReplies(query("reply"));
}

boost::shared_ptr<Client> DataProxy::getClient(unsigned int id) const{
    return resToClient(query("client", "_id", boost::lexical_cast<string > (id)));
}

boost::shared_ptr<Client> DataProxy::getClient(string hash) const{
    return resToClient(query("client", "hash", hash));
}

boost::shared_ptr<Service> DataProxy::getService(unsigned int id) const {
    return resToService(query("service", "_id", boost::lexical_cast<string > (id)));
}

boost::shared_ptr<const vector<boost::shared_ptr<Service> > > DataProxy::getServices() const {
    return resToServices(query("service"));
}

boost::shared_ptr<const vector<boost::shared_ptr<Service> > > DataProxy::getServices(string description) const {
    return resToServices(query("service", "description", description));
}

boost::shared_ptr<const vector<boost::shared_ptr<Service> > > DataProxy::getServicesForClient(std::string hash) const {
    boost::shared_ptr<Client> c = getClient(hash);
    return getServicesForClient(c.get()!=NULL ? c->id : -1);
}

boost::shared_ptr<const vector<boost::shared_ptr<Service> > > DataProxy::getServicesForClient(unsigned int id) const {
    boost::shared_ptr<cppdb::result> res = query(
            "service left join client_service on(service._id=client_service.service_id)",
            "is_active = TRUE and client_id",
            boost::lexical_cast<string > (id)
            );
    return resToServices(res);
}

void DataProxy::setClientService(unsigned int client_id, unsigned int service_id, bool enable) {
    resetQuery();
    queryString << "select count(*) as number from client_service where client_id=" << client_id << " and service_id=" << service_id;
    cppdb::statement stat = db << queryString.str();
    cppdb::result res = stat.query();
    res.next();
    if (res.get<int>("number") > 0) {
        resetQuery();
        queryString << "update client_service set is_active=" << enable
                << " where service_id=" << service_id << " and client_id=" << client_id;
        stat = db << queryString.str();
    } else {
        resetQuery();
        queryString << "insert into client_service values (null," << client_id << "," <<
                service_id << "," << enable << ")";
        stat = db << queryString.str();
    }
    cout << "will exec: " << queryString.str() << endl;
    stat.exec();
}

void DataProxy::setClientService(string client_hash, unsigned int service_id, bool enable) {
    setClientService(getClient(client_hash)->id, service_id, enable);
}

void DataProxy::addRecord(unsigned int client_id, unsigned int service_id, unsigned int reply_id) {
    resetQuery();
    queryString << "insert into record values (null,now(),1," <<
            client_id << "," << service_id << "," << reply_id << ")";
    cppdb::statement stat = db << queryString.str();
    stat.exec();
}

void DataProxy::addRecord(string client_hash, unsigned int service_id, unsigned int reply_id) {
    addRecord(getClient(client_hash)->id, service_id, reply_id);
}

unsigned int DataProxy::addClient(std::string ip, unsigned short port, std::string hash, std::string remarks){
    shared_ptr<Client> c = getClient(hash);
    if(c.get()!=NULL){
        return c->id;
    }
    else{
        resetQuery();
        queryString << "insert into client values (null,'"<<ip<<"',"<<port<<",'"<<hash<<"','"<<remarks<<"')";
        cppdb::statement stat = db<<queryString.str();
        stat.exec();
        return getClient(hash)->id;
    }
}

boost::shared_ptr<Service > DataProxy::resToService(boost::shared_ptr<cppdb::result> res) const{
    Service* s = NULL;
    if (res->next()) {
        s=new Service();
        res->fetch("_id", s->id);
        res->fetch("description", s->description);
    }
    return boost::shared_ptr<Service>(s);;
}

boost::shared_ptr<const std::vector<boost::shared_ptr<Service> > > DataProxy::resToServices(boost::shared_ptr<cppdb::result> res) const {
    shared_ptr<vector<shared_ptr<Service> > > v(new vector<shared_ptr<Service> >);
    while (res->next()) {
        v->push_back(shared_ptr<Service>(new Service()));
        res->fetch("_id", (*(*v)[v->size()-1]).id);
        res->fetch("description", (*(*v)[v->size()-1]).description);
    }
    return v;
}

shared_ptr<const vector<boost::shared_ptr<Reply> > > DataProxy::resToReplies(boost::shared_ptr<cppdb::result> res) const {
    shared_ptr<vector<shared_ptr<Reply> > > v(new vector<shared_ptr<Reply> >);
    while (res->next()) {
        v->push_back(shared_ptr<Reply>(new Reply()));
        res->fetch("_id", (*(*v)[v->size()-1]).id);
        res->fetch("message", (*(*v)[v->size()-1]).message);
    }
    return v;
}

boost::shared_ptr<Client> DataProxy::resToClient(boost::shared_ptr<cppdb::result> res) const {
    Client* c = NULL;
    if (res->next()) {
        c=new Client();
        res->fetch("hash", c->hash);
        res->fetch("_id", c->id);
        res->fetch("ip", c->ip);
        res->fetch("remarks", c->remarks);
        res->fetch("port", c->port);
    }
    return boost::shared_ptr<Client>(c);
}

boost::shared_ptr<const std::vector<boost::shared_ptr<Client> > > DataProxy::resToClients(boost::shared_ptr<cppdb::result> res) const {
    shared_ptr<vector<shared_ptr<Client> > > v(new vector<shared_ptr<Client> >);
    while (res->next()) {
        v->push_back(shared_ptr<Client>(new Client()));
        res->fetch("_id", (*(*v)[v->size()-1]).id);
        res->fetch("hash", (*(*v)[v->size()-1]).hash);
        res->fetch("ip", (*(*v)[v->size()-1]).ip);
        res->fetch("remarks", (*(*v)[v->size()-1]).remarks);
        res->fetch("port", (*(*v)[v->size()-1]).port);
    }
    return v;
}

boost::shared_ptr<cppdb::result> DataProxy::query(string table) const{
    return query(table, "", "");
}

boost::shared_ptr<cppdb::result> DataProxy::query(string table, string column, string value) const {
    DataProxy * fake_this = const_cast<DataProxy*>(this);
    fake_this->resetQuery();
    fake_this->queryString << "select * from " << table;
    if (column.length() > 0) {
        fake_this->queryString << " where " << column << "='" << value << "'";
    }
    boost::shared_ptr<cppdb::result> res(new cppdb::result());
    *res = fake_this->db << (fake_this->queryString.str());
    return res;
}
void DataProxy::deleteClientsData(int client_id){
    resetQuery();
    queryString<<"delete from client_service where client_id="<<client_id<<"; ";
    cppdb::statement stat = db << queryString.str();
    stat.exec();
    resetQuery();
    queryString<<"delete from record where client_id="<<client_id<<"; ";
    stat = db << queryString.str();
    stat.exec();
    resetQuery();
    queryString<<"delete from client where _id="<<client_id<<";";
    stat = db << queryString.str();
    stat.exec();
    resetQuery();
}

int DataProxy::addService(std::string description){
    shared_ptr<const vector<shared_ptr<Service> > > matches = this->getServices(description);
    if(matches.get()!=NULL && matches->size()>0){
        return (*matches->begin())->id;
    }else{
        resetQuery();
        queryString<<"insert into service values(null,'"<<description<<"')"<<endl;
        cppdb::statement stat = db<<queryString.str();
        stat.exec();
        return this->addService(description);
    }
}
